#ifndef _REENTRANT
#define _REENTRANT
#endif

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>


static int master_fd = -1;
pthread_mutex_t accept_con_mutex = PTHREAD_MUTEX_INITIALIZER;

int makeargv(const char *s, const char *delimiters, char ***argvp) {
   int error;
   int i;
   int numtokens;
   const char *snew;
   char *t;

   if ((s == NULL) || (delimiters == NULL) || (argvp == NULL)) {
      errno = EINVAL;
      return -1;
   }
   *argvp = NULL;
   snew = s + strspn(s, delimiters);
   if ((t = malloc(strlen(snew) + 1)) == NULL)
      return -1;
   strcpy(t,snew);
   numtokens = 0;
   if (strtok(t, delimiters) != NULL)
      for (numtokens = 1; strtok(NULL, delimiters) != NULL; numtokens++) ;

   if ((*argvp = malloc((numtokens + 1)*sizeof(char *))) == NULL) {
      error = errno;
      free(t);
      errno = error;
      return -1;
   }

   if (numtokens == 0)
      free(t);
   else {
      strcpy(t,snew);
      **argvp = strtok(t,delimiters);
      for (i=1; i<numtokens; i++)
         *((*argvp) +i) = strtok(NULL,delimiters);
   }
   *((*argvp) + numtokens) = NULL;
   return numtokens;
}

void freemakeargv(char **argv) {
   if (argv == NULL)
      return;
   if (*argv != NULL)
      free(*argv);
   free(argv);
}

/**********************************************
 * init
   - port is the number of the port you want the server to be
     started on
   - initializes the connection acception/handling system
   - YOU MUST CALL THIS EXACTLY ONCE (not once per thread,
     but exactly one time, in the main thread of your program)
     BEFORE USING ANY OF THE FUNCTIONS BELOW
   - if init encounters any errors, it will call exit().
************************************************/
enum boolean {FALSE, TRUE};
struct sockaddr_in ma, ca;
int sock_fd;

void init(int port) {
   // set up sockets using bind, listen
   // also do setsockopt(SO_REUSEADDR)
   // exit if the port number is already in use
   
	sock_fd = 0;

	if ((sock_fd = socket(AF_INET,SOCK_STREAM,0)) == -1)
	{
		perror("Socket not set");
		exit(0);
	}

	int optval = 1;
	setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
	//int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);

	ma.sin_family = AF_INET;
	ma.sin_port = htons(port);
	ma.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(sock_fd, (struct sockaddr*)&ma, sizeof(ma)) == -1)
	{
		perror("Bind failed");
		exit(0);
	}

	listen(sock_fd, 5);
}

/**********************************************
 * accept_connection - takes no parameters
   - returns a file descriptor for further request processing.
     DO NOT use the file descriptor on your own -- use
     get_request() instead.
   - if the return value is negative, the thread calling
     accept_connection must exit by calling pthread_exit().
***********************************************/
int accept_connection(void) {

	// accept one connection using accept()
	// return the fd returned by accept()		

	// Your code goes in this function.
	int newsock;
	pthread_mutex_lock(&accept_con_mutex); //Need locks since the structure of ca and the socket descriptor are shared by threads
	int calen = sizeof(ca);
	newsock = accept(sock_fd, (struct sockaddr *) &ca, &calen);
	pthread_mutex_unlock(&accept_con_mutex);
	return newsock;

}

/**********************************************
 * get_request
   - parameters:
      - fd is the file descriptor obtained by accept_connection()
        from where you wish to get a request
      - filename is the location of a character buffer in which
        this function should store the requested filename. (Buffer
        should be of size 1024 bytes.)
   - returns 0 on success, nonzero on failure. You must account
     for failures because some connections might send faulty
     requests. This is a recoverable error - you must not exit
     inside the thread that called get_request. After an error, you
     must NOT use a return_request or return_error function for that
     specific 'connection'.
************************************************/
int get_request(int fd, char *filename) 
{
   // read from the socketfd and parse the first line for the GET info
   // if it isn't a GET request, then just dump it and return -1.
   // otherwise, store the path/filename into 'filename'.
	FILE *stream = fdopen(fd,"r");
	char buf[1028]; //For GET (space) path/filename(assuming this is up to 1024)
	char **save;
	int compare = 3;
	
	if(fgets(buf,1028,stream) == NULL)
	{
		return 1;
	}
		
	strtok(buf,"\n"); //Get rid of trailing "\n" added from fgets
	
	makeargv(buf," ",&save);
	
	if(strncmp(save[0],"GET",compare) != 0) //Makes sure GET is present
	{
		freemakeargv(save);
		return -1;
	}
	
	if(strlen(save[1]) > 1024) //This would cause the buffer filename to break as it was initilized to 1024 in server.c
	{
		freemakeargv(save);
		return 1;
	}

	if((strstr(save[1],"//") != NULL) || (strstr(save[1],"..") != NULL)) //If filepath has multiple // or .. the code will not allow access to the path
	{
		freemakeargv(save);
		return -1;
	}
	strcpy(filename,save[1]);
	freemakeargv(save);
	
   return 0;

}

/**********************************************
 * return_result
   - returns the contents of a file to the requesting client
   - parameters:
      - fd is the file descriptor obtained by accept_connection()
        to where you wish to return the result of a request
      - content_type is a pointer to a string that indicates the
        type of content being returned. possible types include
        "text/html", "text/plain", "image/gif", "image/jpeg" cor-
        responding to .html, .txt, .gif, .jpg files.
      - buf is a pointer to a memory location where the requested
        file has been read into memory (the heap). return_result
        will use this memory location to return the result to the
        user. (remember to use -D_REENTRANT for CFLAGS.) you may
        safely deallocate the memory after the call to
        return_result (if it will not be cached).
      - numbytes is the number of bytes the file takes up in buf
   - returns 0 on success, nonzero on failure.
************************************************/
int return_result(int fd, char *content_type, char *buf, int numbytes) 
{
   // send headers back to the socketfd, connection: close, content-type, content-length, etc
   // then finally send back the resulting file
   // then close the connection
   
	FILE *stream = fdopen(fd,"w");
	fprintf(stream, "HTTP/1.1 200 OK\nContent-Type: %s\nContent-Length: %d\nConnection: Close\n\n",content_type,numbytes); //Prints the header of the connection

	fwrite(buf,numbytes,1, stream); //File contents, the 1 represents the number of size buf that are being sent, we are only sending 1, thus the 1.

 	fclose(stream);
   	close(fd);
	return 0;
}


/**********************************************
 * return_error
   - returns an error message in response to a bad request
   - parameters:
      - fd is the file descriptor obtained by accept_connection()
        to where you wish to return the error
      - buf is a pointer to the location of the error text
   - returns 0 on success, nonzero on failure.
************************************************/
int return_error(int fd, char *buf) {
   // send 404 headers back to the socketfd, connection: close, content-type: text/plain, content-length
   // send back the error message as a piece of text.
   // then close the connection

   	FILE *stream = fdopen(fd,"w");

   	fprintf(stream,"HTTP/1.1 404 Not Found\nContent-Type: text/html\nContent-Length: %d\nConnection: Close\n\n",strlen(buf));
	fwrite(buf, strlen(buf), 1, stream); //File contents, sent as text and follows same pattern as in return_request. Could of used fprintf again but wanted to stay consistent
	//It should be noted that this assumes that buf will always be a "string" which is why strlen had to be used over sizeof
  	fclose(stream);
  	close(fd);
   /* A 404 error might look like this string, where %d is the content length:
   * "HTTP/1.0 404 Not Found\nContent-Length: %d\nConnection: Close\n\n"
  */

	return 0;

}

